Scope (effect)
Scopeは1つ以上のリソースのlifetimeを表す
Scopeがcloseされると、そのScope内の全てのリソースが解放される
finalizerを指定して、リソースの解放方法を定義できる
リソース管理する言語機能やパターンだmrsekut.icon
docs
#wip
例:スコープを管理する
code:ts
import { Scope, Effect, Console, Exit } from "effect"
const program =
// スコープを作成
Scope.make().pipe(
// ファイナライザ1を追加
Effect.tap((scope) =>
Scope.addFinalizer(scope, Console.log("finalizer 1"))
),
// ファイナライザ2を追加
Effect.tap((scope) =>
Scope.addFinalizer(scope, Console.log("finalizer 2"))
),
// スコープを閉じる
Effect.andThen((scope) =>
Scope.close(scope, Exit.succeed("scope closed successfully"))
)
)
Effect.runPromise(program)
/*
出力:
finalizer 2 ← ファイナライザは逆順に実行される
finalizer 1
*/
Effect.tap
finalizerは、追加された順序の逆順で実行される
スタックの巻き戻しと同じ概念
Scope.make
Scope.addFinalizer
Scope.close
Scopeを閉じる
Scopeのmerge
https://effect.website/docs/resource-management/scope/#manually-create-and-close-scopes
code:ts
import { Effect, Console } from "effect"
const task1 = Effect.gen(function* () {
console.log("task 1")
yield* Effect.addFinalizer(() => Console.log("finalizer after task 1"))
})
const task2 = Effect.gen(function* () {
console.log("task 2")
yield* Effect.addFinalizer(() => Console.log("finalizer after task 2"))
})
const program = Effect.gen(function* () {
// task1とtask2のスコープはマージされる
yield* task1
yield* task2
})
Effect.runPromise(Effect.scoped(program))
/*
出力:
task 1
task 2
finalizer after task 2
finalizer after task 1
*/
task1 と task2 のscopeがまとめられ、逆順でfinalierが実行される
手動にscopeのmakeとcloseもできる
http://effect.website/docs/resource-management/scope/#manually-create-and-close-scopes
Scope.extend
code:ts
const program = Effect.gen(function* () {
const scope1 = yield* Scope.make()
const scope2 = yield* Scope.make()
// task1 を scope1 に拡張
yield* task1.pipe(Scope.extend(scope1))
// task2 を scope2 に拡張
yield* task2.pipe(Scope.extend(scope2))
// scope1 を先にクローズ
yield* Scope.close(scope1, Exit.void)
yield* Console.log("doing something else")
// scope2 をクローズ
yield* Scope.close(scope2, Exit.void)
})
Effect.runPromise(program)
/*
出力:
task 1
task 2
finalizer after task 1
doing something else
finalizer after task 2
*/
Scope.extend
スコープ付きEffectを既存のスコープに延長(mount)する
Effectの実行が終わってもスコープは閉じられない
あるEffectのライフサイクルを外部のスコープに委ねることができる
Scopeをcloseしても、その中のタスクは中断されない
https://effect.website/docs/resource-management/scope/#manually-create-and-close-scopes
Effect.acquireRelease
操作の順次実行パターン
前の操作の成功に依存するような段階的な処理では、途中で失敗したときに、rollbackしたい
https://effect.website/docs/resource-management/scope/#example-pattern-sequencing-operations
↑ここに例が書いているので、一部抜粋
S3バケットをcreateする関数
code:ts
const createBucket = Effect.gen(function* () {
const { createBucket, deleteBucket } = yield* S3;
return yield* Effect.acquireRelease(createBucket, (bucket, exit) =>
Exit.isFailure(exit) ? deleteBucket(bucket) : Effect.void
);
});
Effect.acquireReleaseを使ってる
release時の処理を失敗したかどうかで書き分けている
失敗した場合はそのbucketを削除して、rollbackしている
S3 bucket作成に失敗するtestを書く
code:ts
const S3Test = Layer.effect(
S3,
Effect.gen(function* () {
const failureCase = yield* FailureCase;
return {
createBucket: Effect.gen(function* () {
console.log("S3 バケットを作成中");
if (failureCase === "S3") {
return yield* Effect.fail(new S3Error());
} else {
return { name: "<bucket.name>" };
}
}),
deleteBucket: (bucket) =>
Console.log([S3] バケット ${bucket.name} を削除),
};
})
);